fig-financial-payments-b2b-xapi

(0 reviews)

Webhook Delivery – Encrypted Payment Status Notifications

Introduction

The Fidelidade sends real-time webhook notifications whenever a payment session changes status. Instead of polling the API, your system receives automatic HTTPS POST callbacks containing the updated status.

Because payment sessions may include sensitive information, webhooks are delivered using end-to-end encryption (AES-256/GCM). This ensures confidentiality and integrity of the data during transmission.

Each webhook contains:

  • Encrypted payload (ciphertext) in the HTTP body
  • Encryption metadata in headers:
    • X-IV
    • X-AuthTag
    • X-Idempotency-Key referencing the original payment intent

Encryption is performed using the same encryptionKey that was sent to the partner during the provisional receipt step.

Partners must store the following pair during the payment intent request:

  • Idempotency-Key
  • encryptionKey

These two elements are required to decrypt any webhook related to that payment.


Webhook Encryption Model

During the insurance contracting flow, the platform returns:

  • receiptToken — an opaque, unreadable token containing internal receipt data
  • encryptionKey — a Base64-encoded AES-256 key used to encrypt:
    • the payment intent payload sent by the partner
    • the webhook payload sent back to the partner

For every webhook delivery:

  • A new IV (12 bytes, Base64) is generated
  • A new AuthTag (16 bytes → Base64) is produced
  • The same encryptionKey associated with the original receipt is reused
  • The actual JSON payload is encrypted with AES-256/GCM

GCM mode ensures confidentiality and tamper-proof integrity.


Webhook Request Structure

Required HTTP Headers

HeaderDescription
X-Idempotency-KeyThe same Idempotency-Key originally sent by the partner when creating the payment intent. Identifies the payment session.
X-IVBase64-encoded AES-GCM Initialization Vector (12 bytes).
X-AuthTagBase64-encoded AES-GCM authentication tag (16 bytes).
AuthorizationAuthentication method configured during onboarding (Basic, API Key, or OAuth2 Client Credentials).

HTTP Body

The webhook HTTP body contains only the ciphertext (Base64).

Example:

BEm45DWVy4/7cVsrEgGP6XZGaCnZUcLotXgu70lXXFbeKSak1QMPKejnZ17yFgLmAJscuAf4o7Y=

Internal JSON Payload (before encryption)

This is the structure you will receive after decrypting the ciphertext using:

  • encryptionKey
  • X-IV
  • X-AuthTag

Your system must perform the decryption locally.

Example: payment.succeeded

{
  "eventId": "a8ca3d79-c28d-4302-9414-b3433f6d40ec",
  "eventType": "payment.succeeded",
  "timestamp": "2024-11-04T18:45:23Z",
  "paymentStatus": "Succeeded",
  "error": null
}

Example: payment.declined

{
  "eventId": "b9db4e80-d39e-4413-9525-c4544f7d51fd",
  "eventType": "payment.declined",
  "timestamp": "2024-11-04T18:46:12Z",
  "paymentStatus": "Declined",
  "error": {
    "code": "DECLINED",
    "message": "The payment was rejected by the payer."
  }
}

Field Definitions

FieldDescription
eventIdInternal unique identifier used to correlate logs in case of issues.
eventTypeEvent type (payment.succeeded, payment.declined, payment.failed, payment.expired).
timestampISO-8601 UTC timestamp when the event occurred.
paymentStatusFinal normalized status: Pending, Succeeded, Declined, Expired, Failed.
errorObject containing code and message when the payment is unsuccessful.

Decryption Responsibilities

Your system must:

  1. Read:

    • X-IV
    • X-AuthTag
    • Webhook ciphertext (body)
    • encryptionKey stored during the intent creation
  2. Perform AES-256/GCM decryption

  3. Validate the resulting JSON

Fidelidade does not send plaintext payloads through webhook.


Reference Java Decryption Example

This is a minimal example showing how partners can decrypt webhook payloads using AES-256/GCM.

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class ShortAesGcmDecryptExample {

    public static void main(String[] args) throws Exception {

        String encryptionKey = "<BASE64_ENCRYPTION_KEY>";
        String ivBase64 = "<X-IV>";
        String tagBase64 = "<X-AuthTag>";
        String ciphertextBase64 = "<WEBHOOK_CIPHERTEXT>";

        byte[] keyBytes = Base64.getDecoder().decode(encryptionKey);
        byte[] iv = Base64.getDecoder().decode(ivBase64);
        byte[] tag = Base64.getDecoder().decode(tagBase64);
        byte[] cipherBytes = Base64.getDecoder().decode(ciphertextBase64);

        byte[] combined = new byte[cipherBytes.length + tag.length];
        System.arraycopy(cipherBytes, 0, combined, 0, cipherBytes.length);
        System.arraycopy(tag, 0, combined, cipherBytes.length, tag.length);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);

        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), spec);

        String json = new String(cipher.doFinal(combined), "UTF-8");

        System.out.println("Decrypted JSON: " + json);
    }
}

This logic can be replicated in any language (Node.js, Python, .NET, Go, PHP, Rust, etc.) that supports AES-GCM.

Best Practices

  • Always generate a new IV for each encryption operation (the platform already does this for webhooks).
  • Store the pair Idempotency-Key + encryptionKey for all payment intents.
  • Use eventId to guarantee idempotent processing on your side.
  • Return 200 OK as soon as you receive the webhook (process internally afterwards).
  • Reject any webhook that:
    • fails authentication
    • contains invalid Base64
    • fails AES-GCM integrity validation
  • Never attempt to read or interpret the receiptToken.

Reviews